pollux's Dairy

Android-6.0.0_r1源码分析-Java层加载Dex和类的过程

字数统计: 2.6k阅读时长: 16 min
2019/12/14 Share

PROLOGUE

1
2
3
4
5
6
7
8
9
10
private Dynamic dynamic;

DexClassLoader dexClassLoader = new DexClassLoader(dexPath,optimizedDirectory, libraryPath, getClassLoader());
Class Clazz = dexClassLoader.loadClass("com.example.myapplication.Dynamic");
dynamic = (Dynamic) Clazz.newInstance();

dynamic.sayHello();

Method m = Clazz.getDeclaredMethod("sayHello");
m.invoke();

在android中,可以通过上面两行代码动态加载dex文件,并加载其中的类,调用类中的方法。记录一下动态加载Dex和类的过程。

动态加载的基础是ClassLoader—类加载器,ClassLoader 就是专门用来处理类加载工作的,一个运行中的 APP 不仅只有一个类加载器。

在 Java 中,只有当两个实例的类名、包名以及加载其的 ClassLoader 都相同,才会被认为是同一种类型

一、创建类加载器的过程

在Android中,ClassLoader是一个抽象类,定义为:public abstract class ClassLoader
在实际开发中,一般使用其子类DexClassLoaderPathClassLoader来创建类加载器实例,进而加载类,需要说明的是,这两个类都继承于BaseDexClassLoaderBaseDexClassLoader又继承于ClassLoader,他们之间的关系如下,图片来自gityuan

img

1
2
3
public class DexClassLoader extends BaseDexClassLoader
public class PathClassLoader extends BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader

DexClassLoader类和PathClassLoader类的区别在下面分析的时候会说

首先看他这两个类的构造函数:

DexClassLoader

1
2
3
4
5
6
/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

PathClassLoader

1
2
3
4
5
6
7
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}

这两个类的构造函数都直接使用了父类的构造函数,但是传参不同,DexClassLoader设置了optimizedDirectory,而PathClassLoader该参数设为null。
BaseDexClassLoader构造函数:

1
2
3
4
5
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

使用super(parent)创建了对象,然后使用makePathElements创建了DexPathList对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
......
this.definingContext = definingContext;
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
......
}

/**
* Makes an array of dex/resource path elements, one per element of the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
......
......
else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
dex = loadDexFile(file, optimizedDirectory);
}
}//endif if (file.isFile())

if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}//endfor for (File file : files)
return elements.toArray(new Element[elements.size()]);
}


/**
* Constructs a {@code DexFile} instance, as appropriate depending
* on whether {@code optimizedDirectory} is {@code null}.
*/
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}


/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
private static String optimizedPathFor(File path,
File optimizedDirectory) {
/*
* Get the filename component of the path, and replace the
* suffix with ".dex" if that's not already the suffix.
*/
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}

上面4个函数

DexPathList构造函数,将整个dexPath路径分成包含多个Dex文件路径的list作为参数传给了makePathElements。

makePathElements根据dex的path,将所有的dex都封装在了数组中,然后调用loadDexFile加载dex文件。

loadDexFile创建DexFile对象。若optimizedDirectory不为NULL,将dex文件生成在optimizedDirectory路径中,使用DexFile类的loadDex函数创建对象;若optimizedDirectory为NULL,那么会直接使用 dex 文件原有的路径来创建 DexFile对象。

optimizedPathFor主要处理了源文件后缀的问题,让其后缀为.dex。当optimizedDirectory不为NULL时,在optimizedDirectory目录创建新文件,并将目录返回。

DexClassLoader类和PathClassLoader类的区别

DexClassLoader类和PathClassLoader类的区别主要在于构造对象时optimizedDirectory是否为NULL。

由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,optimizedDirectory 必须是一个内部存储路径,optimizedDirectory参数就是指定解压出的dex文件存放的路径,PathClassLoader类构造对象时optimizedDirectory参数为空,则说明其不需要解压提取dex文件,所以其直接加载内部的dex文件;而DexClassLoader类可以指定optimizedDirectory参数,说明其需要从外部解压提取dex文件。总结如下:

DexClassLoader:能够加载未安装的jar/apk/dex
PathClassLoader:加载Android系统类和系统中应用的类

回到makePathElements函数,将DexFile对象和路径等信息封装成了数组返回给了DexPathList构造函数

1
2
    elements.add(new Element(dir, false, zip, dex));
return elements.toArray(new Element[elements.size()]);

然后在BaseDexClassLoader函数中通过DexPathList类的实例对象pathList保存了这些DexFile对象

1
private final DexPathList pathList;

二、加载类的过程

JVM 中 ClassLoader 通过 defineClass 方法加载 jar 里面的 Class,而 Android 中这个方法被弃用了。

1
2
3
4
5
@Deprecated
protected final Class<?> defineClass(byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}

取代的是loadClass方法。第一节中通过实例化类加载器ClassLoader,创建了DexFile对象,并把对象保存在了pathList实例对象中。在Android中,使用loadClass方法加载一个类,这个方法定义在ClassLoader类中,其派生类没有重写该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/libcore/libart/src/main/java/java/lang/ClassLoader.java

/**
* Loads the class with the specified name. Invoking this method is
* equivalent to calling {@code loadClass(className, false)}.
*/
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}

/**
* Loads the class with the specified name, optionally linking it after
* loading.
*/
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}

ClassLoader 双亲委派模型加载类的过程

上面第二个loadClass方法使用了双亲委派模型,loadClass在加载一个类时

1、判断当前的类加载器ClassLoader是否加载过此类,有就直接返回
2、若未加载过当前类,就去查询Parent是否加载过此类
3、如果继承路线上的ClassLoader都没有加载,则调用findClass去加载这个类
即如果一个类被位于树根的ClassLoader加载过,那么这个类在一个虚拟机的运行周期内永远不会被重新加载。

作用

一个Class的标识为包名PackageName+类名ClassName+类加载器ClassLoader

首先是共享功能,一些 Framework 层级的类一旦被顶层的 ClassLoader 加载过就缓存在内存里面,以后任何地方用到都不需要重新加载。

除此之外还有隔离功能,不同继承路线上的 ClassLoader 加载的类肯定不是同一个类,这样的限制,避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。这也好理解,一些系统层级的类会在系统初始化的时候被加载,比如 java.lang.String,如果在一个应用里面能够简单地用自定义的 String 类把这个系统的 String 类给替换掉,那将会有严重的安全问题。

loadClass方法调用了findClass方法,ClassLoader类的findClass方法会报异常,而BaseDexClassLoader 重载了这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}

上面在创建类构造器的过程中,已经创建好了所有的DexFile对象,并封装在了BaseDexClassLoader类中的一个实例对象pathList中:

1
private final DexPathList pathList;

BaseDexClassLoader的findClass方法直接调用了DexPathList类的findClass方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*/
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}

在DexPathList类的findClass方法中,遍历其dexElements变量中保存的DexFile对象,这个dexElements变量在DexPathList类的构方法中被初始化,由makePathElements方法生成,保存了DexFile对象。

然后接着调用了DexFile对象的loadClassBinaryName方法,我们找到DexFile类的该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/libcore/dalvik/src/main/java/dalvik/system/DexFile.java

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}

private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}

loadClassBinaryName方法是对同类下defineClass方法的封装,在defineClass方法内,又调用了native层的defineClassNative方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/art/runtime/native/dalvik_system_DexFile.cc

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,jobject cookie) {
std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
ScopedUtfChars class_name(env, javaName);
......
......
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (auto& dex_file : *dex_files) {
const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(*dex_file);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,class_loader, *dex_file, *dex_class_def);
}
}
return nullptr;
}

DexFile_defineClassNative方法中。可以看到通过调用Runtime类的静态成员函数Current获得了Runtime单例,在ART虚拟机进程中,存在着一个Runtime单例,用来描述ART运行时。获得了这个单例之后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及链接类方法。然后调用了class_linker的DefineClass来获得class对象并返回,至此类加载就完成了。

CATALOG
  1. 1. PROLOGUE
  2. 2. 一、创建类加载器的过程
  3. 3. 二、加载类的过程